﻿/*
* Copyright 2009-2010 AKABANA.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
using System;
using System.Globalization;
using System.IO;
using System.Text;
using Akabana.Amf.Amf0.IO;
using Akabana.Amf.Exception;
using Akabana.Amf.IO;
using Akabana.Amf.Serializer;
using Akabana.Amf.Util;

namespace Akabana.Amf.Amf0.Serializer
{

    public class Amf0Serializer : AbstractAmfSerializer
    {
        private const byte BOOLEAN_TRUE = 1;

        private const byte BOOLEAN_FALSE = 0;

        public AbstractAmfSerializer amfSerializer = null;

        public Amf0Serializer(AmfWriter AmfWriter )
            : base(AmfWriter)
        {
        }

        public override void referenceClear()
        {
            base.referenceClear();
            if (amfSerializer != null)
            {
                amfSerializer.referenceClear();
            }
        }

        protected override IAmfReferenceTable CreateReferenceTable()
        {
            return new Amf0ReferenceTable();
        }

        public override void Serialize(object value)
        {
            this.referenceClear();
            this.DoEncode(value);

        }

        private void DoEncode(object value )
        {

            if (value == null)
            {
                this.DoEncodeNull();
                return;
            }

            var objectType = value.GetType();
            var objectCode = Type.GetTypeCode(objectType);
            switch (objectCode)
            {
                case TypeCode.Decimal:
                case TypeCode.Double:
                case TypeCode.SByte:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.Byte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                    this.DoEncodeNumber(Convert.ToDouble(value, CultureInfo.CurrentCulture));
                    break;
                case TypeCode.String:
                    this.DoEncodeString((String)value);
                    break;
                case TypeCode.Boolean:
                    this.DoEncodeBoolean((bool)value);
                    break;
                case TypeCode.DateTime:
                    this.DoEncodeDate((DateTime)value);
                    break;
                case TypeCode.Object:
                    this.DoEncodeAvmplusObject(value);
                    break;
                default:
                    throw new UnsupportTypeException(objectType.Name);
            }
        }

        private void DoEncodeNumber(double value )
        {
            amfWriter.WriteTypeMarker(Amf0TypeMarker.NumberMarker);
            amfWriter.WriteNumber(value);
        }

        private void DoEncodeString(string value )
        {
            var byteData = Encoding.UTF8.GetBytes(value);
            var stringSize = byteData.Length;

            if (stringSize >= Int32.MaxValue)
            {
                throw new StringOutOfRangeException(stringSize);
            }

            if (stringSize >= UInt16.MaxValue)
            {
                //LongString
                amfWriter.WriteTypeMarker(Amf0TypeMarker.LongStringMarker);
                amfWriter.WriteU32((uint)stringSize);
                amfWriter.Write(byteData);
            }
            else
            {
                //String
                amfWriter.WriteTypeMarker(Amf0TypeMarker.StringMarker);
                amfWriter.WriteU16((ushort)stringSize);
                amfWriter.Write(byteData);
            }
        }

        private void DoEncodeBoolean(bool value )
        {
            amfWriter.WriteTypeMarker(Amf0TypeMarker.BooleanMarker);
            if (value)
            {
                amfWriter.Write(BOOLEAN_TRUE);
            }
            else
            {
                amfWriter.Write(BOOLEAN_FALSE);
            }
        }

        private void DoEncodeDate(DateTime value )
        {
            amfWriter.WriteTypeMarker(Amf0TypeMarker.DateMarker);
            amfWriter.WriteS16(0);
            double time = DateUtil.ToUtcMillSec(value);
            amfWriter.WriteNumber(time);
        }

        private void DoEncodeNull( )
        {
            amfWriter.WriteTypeMarker(Amf0TypeMarker.NullMarker);
        }

        private void DoEncodeUndefined( )
        {
            amfWriter.WriteTypeMarker(Amf0TypeMarker.UndefinedMarker);
        }

        private void DoEncodeStrictArray(Array value )
        {
            amfWriter.WriteTypeMarker(Amf0TypeMarker.StrictArrayMarker);
            amfWriter.WriteU32((uint)value.Length);

            foreach (var item in value)
            {
                this.DoEncode(item);
            }
        }

        private void DoEncodeAvmplusObject(object value )
        {
            if (amfSerializer == null)
            {
                throw new UnsupportTypeException(Amf0Consts.AVMPLUS_OBJECT);
            }
            amfWriter.WriteTypeMarker(Amf0TypeMarker.AvmplusObjectMarker);
            amfSerializer.Serialize(value);
        }
    }
}
